#!/bin/sh

# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Provides alert messages in boot stage, called by chromeos_startup.

# Two instances of this script should never be run in parallel: the alert
# animations will fight with each other, and there is a potential race in the
# emission of the boot-alert-request signal (see http://crosbug.com/33838).

# Since this script only provides messages, never abort.
set +e

# Prints usage help for commands supports
usage_help() {
  echo "Usage: $0 mode terminal [arg ...]

  warn_dev: Message for warning about developer mode on normal firmware.
            Arg #1: (optional, default=30) Countdown in seconds before continue

  enter_dev: Message for entering developer mode from non-dev.
             Arg #1: (optional, default=30) Countdown in seconds before continue

  leave_dev: Message when leaving developer mode.
             Arg: none

  update_firmware: Message before starting firmware update.
             Arg: none

  wipe:      Message when starting to erase stateful partition.
             Arg #1: (optional) Image file to show.

  power_wash: Message when users wants to wipe the stateful partition.
              Arg: none

  self_repair: Message when starting to rebuild stateful partition.

  dev_fwcheck: Message when staring with developer firmware, to check if there
               is available updates.

  block_devmode: Message shown to indicate that dev mode is blocked by request
                 of the device owner.
                 Arg: none
"
}

# Prints out system locale by searching cached settings or VPD.
find_current_locale() {
  # TODO(hungte) Find some better way other than hard coding file path here.
  local state_file='/mnt/stateful_partition/home/chronos/Local State'
  # VPD cache file is generated by src/platform/util/dump_vpd_log.
  local vpd_file='/mnt/stateful_partition/unencrypted/cache/vpd/filtered.txt'
  local locale=""
  if [ -f "$state_file" ]; then
    locale="$(grep -w '"app_locale":' "$state_file" |
              sed 's/.*"\([^"]*\)",$/\1/')" || locale=""
  fi
  if [ -n "$locale" ]; then
    echo "$locale"
  elif [ -f "$vpd_file" ]; then
    sed -nr '/^"initial_locale"=".*"/s/.*="(.*)"/\1/p' "$vpd_file"
  else
    vpd -i RO_VPD -g initial_locale 2>/dev/null || true
  fi
}

# Shows boot messages in assets folder on screen center if available.
# Arguments: message in /usr/share/chromeos-assets/text/boot_messages/$locale
show_assets_message_image() {
  local message="$1"
  local locale locale_list

  # Build locale list
  locale="$(find_current_locale)" || locale=""
  # Starting from R34, the initial_locale from VPD may have multiple values,
  # separated by ',' -- and we only want to try the primary one.
  locale="${locale%%,*}"
  locale_list="$locale"
  while [ "${locale%[-_]*}" != "$locale" ]; do
    locale="${locale%[-_]*}"
    locale_list="$locale_list $locale"
  done
  locale_list="$locale_list en-US en"

  display_boot_message "$message" "$locale_list" || return "$?"
}

# Prints the two byte hex code of the matched char or
# exists non-zero on timeout.  It reads from the current tty.
# To override, call it as follows,
#   match_char_timeout args < "$STDOUT"
#
# Arguments: time_in_seconds two_byte_hex_match_1 two_byte_hex_match_2 ...
match_char_timeout() {
  local delay_secs="$1"
  shift

  local input=''
  local match=''
  local start_time=$(date +%s)
  local stop_time=$((start_time + delay_secs))
  local tty_config=$(stty -g)
  stty raw -echo
  while [ $delay_secs -gt 0 ]; do
    input=$(timeout -s KILL ${delay_secs}s head -c 1)
    [ $? -eq 137 ] && break  # Timed out.
    input=$(printf "%02x" "'$input")
    for char in "$@"; do
      if [ "$input" = "$char" ]; then
        match="$input"
        break
      fi
    done
    [ -n "$match" ] && break
    delay_secs=$((stop_time - $(date +%s) ))
  done
  # Restores the tty's settings.
  stty $tty_config

  [ -z "$match" ] && return 1
  printf "$match"
  return 0
}

# Returns if current device is using virtual developer switch.
has_virtual_dev_switch() {
  local VBSD_HONOR_VIRT_DEV_SWITCH="0x400"
  local vdat_flags="$(crossystem vdat_flags || echo 0)"
  [ "$((vdat_flags & VBSD_HONOR_VIRT_DEV_SWITCH))" != "0" ]
}

# Prints message when in developer mode
# Argument: time to countdown (in seconds)
mode_warn_dev() {
  local delay_secs="${1:-30}"
  # Don't show a broken/partial background image.
  tput clear > "$STDOUT"
  echo '






                        __________________________
                       |.------------------------.|
                       ||                        ||
                       ||    (_)          (_)    ||
                       ||                        ||
                       ||       __________       ||
                       ||     /            \     ||
                       ||________________________||
                      / __/___/_____________\__\__ \
                     /               _              \
                    /        /_______\ \_____\       \
                   /__________________\ \_____________\
                   \___________________\ \____________/
                                        \ \_-_-_-.
                                      /|         |
                                     |  |        |
                                       \         |
                                        \_______/

                   Chrome OS verification is turned off.
                     Press space to begin recovery.
  ' >"$STDOUT"
  # TODO(wad) Use the developer mode firmware image

  # Read a space bar or Ctrl+D or timeout.
  local input=$(match_char_timeout "$delay_secs" 04 20 < "$STDOUT")
  local exit_code=$?
  tput clear > "$STDOUT"
  # If we timed out, we're done.
  [ $exit_code -ne 0 ] && return 0
  case "$input" in
    "04")  # Ctrl+D
      # Done.
      ;;
    "20")  # Spacebar
      crossystem recovery_request=1
      reboot
      # To prevent the system from continuing to boot.
      sleep infinity
      ;;
  esac
  return 0
}


# Prints message when entering developer mode
# Argument: time to countdown (in seconds)
mode_enter_dev() {
  local delay_secs="${1:-30}"

  # The text below is only fallback of localized assets message.
  # Please sync with text there if you want to change the message.
  local enter_dev1_text="
    Local data will be cleared.

    Modifications you make to the system are not supported by Google, may cause
    hardware issues and may void warranty.

    To cancel, turn your computer off now and toggle the Developer Switch back
    to Verified Mode.
  "
  local enter_dev1_virtual_text="
    Your system is transitioning to Developer Mode.
    Local data has been cleared.

    Modifications you make to the system are not supported by Google, may cause
    hardware issues and may void warranty.

    To cancel, turn your computer off now.
  "
  if has_virtual_dev_switch; then
    show_assets_message_image "enter_dev1_virtual" ||
      echo "$enter_dev1_virtual_text" >"$STDOUT"
  else
    show_assets_message_image "enter_dev1" ||
      echo "$enter_dev1_text" >"$STDOUT"
  fi

  local format='\r  %-30s'
  for dev_count_down in $(seq $delay_secs -1 1); do
    # Trailing spaces must exist to clear previous message when the printed
    # counter width changed (ex, 100->99).
    # TODO(hungte) merge this with assets messages so it can be localized.
    printf "$format" "Starting in $dev_count_down second(s)..." >"$STDOUT"
    sleep 1
  done

  # Count-down
  tput clear >"$STDOUT" &&
  show_assets_message_image "enter_dev2" ||
  # The text by echo below is only fallback of localized assets message.
  # Please sync with text there if you want to change the message.
  echo "
    Preparing system for Developer Mode (may take 5 - 10 minutes).
    Do not turn your computer off until it has restarted.

    On reboot, press Ctrl+Alt+[->] (F2) for information about Developer Mode.
  " >"$STDOUT"
  # TODO(wad,wfrichar) Request a root password here.
  # TODO(wad,reinauer) Inform the user of chromeos-firmwareupdate --mode=todev
}

# Prints message when leaving developer mode
mode_leave_dev() {
  show_assets_message_image "leave_dev" ||
  # The text by echo below is only fallback of localized assets message.
  # Please sync with text there if you want to change the message.
  echo "
    Your system last booted in Developer Mode.
    Returning to Normal (Verified) Mode...
    Upon completion, the system will reboot.
  " >"$STDOUT"
}

# Prints messages before starting firmware update
mode_update_firmware() {
  show_assets_message_image "update_firmware" ||
  # The text by echo below is only fallback of localized assets message.
  # Please sync with text there if you want to change the message.
  echo "
    Your system is applying a critical update.
    Please do not turn it off.
  " >"$STDOUT"
}

# Prints message before starting to erase stateful partition (wipe).
# Argument: (optional) file for splash image to show
mode_wipe() {
  local splash_file="$1"

  if [ -s "$splash_file" ] && type ply-image >/dev/null 2>&1; then
    # Do not use background execution because this file is going to be wiped.
    ply-image "$splash_file"
  else
    echo "    Erasing stateful partition..." >"$STDOUT"
  fi
}

# Prints messages before starting user-initiated wipe.
mode_power_wash() {
  show_assets_message_image "power_wash" ||
  # The text by echo below is only fallback of localized assets message.
  # Please sync with text there if you want to change the message.
  echo "
    Powerwash in progress. Do not switch off your device.

    Powerwashes happen when critical errors are detected, or when you choose
    to reset your device. This will reset Chrome OS to be just like new, and
    you'll be back in operation in just a minute.
  " >"$STDOUT"
}


# Prints message before starting to rebuild a corrupted stateful partition.
mode_self_repair() {
  show_assets_message_image "self_repair" ||
  # The text by echo below is only fallback of localized assets message.
  # Please sync with text there if you want to change the message.
  echo "
    Your system is repairing itself.  Please wait.
  " >"$STDOUT"
}

# Prints message when starting with developer firmware, to check if there's
# available updates for firmware.
mode_dev_fwcheck() {
  # TODO(hungte) Find some better way to store firmware value, like using
  # /etc/lsb-release.  Currently the firmware updater may contain fields like
  # TARGET_* in the beginning of updater script.
  local TARGET_FWID=""
  local TARGET_ECID=""
  # The magic value 40 is verified on current updaters (using line 23-24)
  eval "$(head -40 /usr/sbin/chromeos-firmwareupdate |
          grep '^ *TARGET_..ID=')"
  if [ -z "$TARGET_FWID" -a -z "$TARGET_ECID" ]; then
    return
  fi

  local fwid="$(crossystem fwid)"
  local ec_info
  ec_info="$(mosys -k ec info 2>/dev/null)" || ec_info=""
  local ecid="$(fw_version="Unknown"; eval "$ec_info"; echo "$fw_version")"

  # Ignore known firmware images carried by updaters with multiple images
  # TODO(hungte) Replace this by a "compatible list" in updater if such request
  # becomes a common feature.
  case "$(mosys platform name 2>/dev/null):$ecid" in
    "ZGB:0.14" | "Alex:00VFA616" )
      TARGET_ECID="$ecid"
      ;;
  esac

  local notify_update=0
  if [ "$TARGET_FWID" != "$fwid" ] &&
     [ "$TARGET_FWID" != "IGNORE" ]; then
    notify_update=1
    echo "
      System firmware update available: [$TARGET_FWID]
      Currently installed system firmware: [$fwid]
    " >"$STDOUT"
  fi
  if [ "$TARGET_ECID" != "$ecid" ] &&
     [ "$TARGET_ECID" != "IGNORE" ]; then
    notify_update=1
    echo "
      EC firmware update available: [$TARGET_ECID]
      Currently installed EC firmware: [$ecid]
    " >"$STDOUT"
  fi
  if [ $notify_update -ne 0 ]; then
    echo "
      Firmware auto updating is disabled for developer mode. If you want to
      manually update your firmware, please run the following command from a
      root shell:

      sudo chromeos-firmwareupdate --force --mode=recovery
    " >"$STDOUT"
  fi
}

# Prints a message telling the user that developer mode has been disabled for
# the device upon request by the device owner. Depending on hardware support,
# the system switches back to verified boot mode automatically or prompts the
# user to do so. The system reboots after the user confirms by pressing space or
# after timeout.
mode_block_devmode() {
  local delay_secs=30

  if has_virtual_dev_switch; then
    show_assets_message_image "block_devmode_virtual" ||
    # The text by echo below is only fallback of localized assets message.
    # Please sync with text there if you want to change the message.
    echo "
      The device owner has disabled Developer Mode for this device. Please ask
      the device owner to remove the restriction if you want to use Developer
      Mode.

      The system will reboot in Verified Mode.
    " >"$STDOUT"
  else
    # Leave the notification on the screen for 5 minutes to increase chances
    # the user actually sees it before shutting down the device.
    delay_secs=300
    show_assets_message_image "block_devmode" ||
    # The text by echo below is only fallback of localized assets message.
    # Please sync with text there if you want to change the message.
    echo "
      The device owner has disabled Developer Mode for this device. Please ask
      the device owner to remove the restriction if you want to use Developer
      Mode.

      Please toggle the Developer Switch and reboot the device to return to
      Verified Mode.
    " >"$STDOUT"
  fi

  # Read a space bar or timeout.
  local input=$(match_char_timeout "$delay_secs" 20 < "$STDOUT")
  tput clear > "$STDOUT"

  if has_virtual_dev_switch; then
    # Return to verified mode.
    crossystem disable_dev_request=1
    reboot
  else
    # Shut down instead of rebooting in a loop.
    halt
  fi

  # To prevent the system from continuing to boot.
  sleep infinity
}

# Main initialization and dispatcher
main() {
  # process args
  if [ $# -lt 2 ]; then
    usage_help
    exit 1
  fi
  local mode="$1"
  # global
  STDOUT="$2"
  shift
  shift

  # Wait until the boot-alert-ready abstract job has started, indicating that
  # it's safe to display an alert onscreen.
  if ! initctl status boot-alert-ready | grep -q running; then
    initctl emit boot-alert-request
  fi

  # Light up the screen if possible.
  backlight_tool --set_brightness_percent 100 || true

  # Hides cursor and prevents console from blanking after long inactivity.
  setterm -cursor off -blank 0 -powersave off -powerdown 0 >"$STDOUT" || true

  case "$mode" in
    "warn_dev" | "enter_dev" | "leave_dev" | "dev_fwcheck" | \
      "update_firmware" | "wipe" | "power_wash" | "self_repair" | \
      "block_devmode" )
      mode_"$mode" "$@"
      ;;
    * )
      usage_help
      exit 1
      ;;
  esac
}

# Main Entry
main "$@"
